#include <sstream>
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <openssl/err.h>
#include <sys/types.h>      // typy danych do wywolan systemowych
#include <sys/socket.h>     // gniazda BSD
#include <netinet/in.h>     // adresy
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include "SSLServerSession.h"
#include "../CommError.h"

#define foreach BOOST_FOREACH

using namespace kc_sf;

// ---------------------------------------------------------------- //

SSLServerSession::SSLServerSession( int accept_socket )
: ssl_accept_socket( accept_socket ),
  ssl_accept_ssl( NULL ),
  ssl_ctx( NULL )
{
	SSL_library_init();
	ERR_load_BIO_strings();
	ERR_load_SSL_strings();
	OpenSSL_add_all_algorithms();

    // SIGPIPE handler
    signal(SIGPIPE,sigpipeHandle);

    // utworzenie kontekstu
    ssl_ctx = SSL_CTX_new( SSLv23_method() );

    // wczytanie kluczy i certyfikatow
    int cert = SSL_CTX_use_certificate_chain_file( ssl_ctx,"./certs/server_certificate.pem" );
    if( !cert )
    	throwSSLError( FailedInit, cert, "Błąd przy pobieraniu certyfikatu serwera SSL" );

    SSL_CTX_set_default_passwd_cb( ssl_ctx, getPasswordCallback);
    int key = SSL_CTX_use_PrivateKey_file( ssl_ctx,"./certs/server_private_key.pem",SSL_FILETYPE_PEM );
    if( !key )
		throwSSLError( FailedInit, key, "Błąd przy pobieraniu klucza prywatnego serwera SSL" );
}

// ---------------------------------------------------------------- //

SSLServerSession::~SSLServerSession()
{
	if( ssl_accept_ssl )
		close();

	if( ssl_ctx != NULL )
		SSL_CTX_free( ssl_ctx );
}

// ---------------------------------------------------------------- //

SSL * SSLServerSession::acceptClient ()
{
	if( ssl_accept_ssl )
		return ssl_accept_ssl;

	ssl_accept_ssl = SSL_new ( ssl_ctx );
	SSL_set_fd( ssl_accept_ssl, ssl_accept_socket );
	SSL_set_accept_state( ssl_accept_ssl ); // server mode

	// accept_ssl handshake
	int err = SSL_accept( ssl_accept_ssl );
	if (err <= 0)
		throwSSLError( FailedConnection, err, "Błąd SSL_accept." );

    return ssl_accept_ssl;
}

// ----------------------------------------------------------- //

void SSLServerSession::close()
{
	int r = SSL_shutdown( ssl_accept_ssl );
	if( !r )
	{
		/* If we called SSL_shutdown() first then
		we always get return value of ’0’. In
		this case, try again. */
		r = SSL_shutdown( ssl_accept_ssl );
	}

	switch( r )
	{
		case 1:
			break; /* Success */
		case 0:
		case -1:
		default:
			break;
			// shutdown failed
	}

	SSL_free( ssl_accept_ssl );
	ssl_accept_ssl = NULL;
}

// ---------------------------------------------------------------- //

void SSLServerSession::throwSSLError( CommErrorType type, int err, const std::string &err_msg )
{
	std::stringstream err_stream;
	int err_no = SSL_get_error( ssl_accept_ssl, err );
	const std::string err_type =
		err_no == SSL_ERROR_ZERO_RETURN ? "SSL_ERROR_ZERO_RETURN"
		: err_no == SSL_ERROR_WANT_READ ? "SSL_ERROR_WANT_READ"
		: err_no == SSL_ERROR_WANT_WRITE ? "SSL_ERROR_WANT_WRITE"
		: err_no == SSL_ERROR_WANT_CONNECT ? "SSL_ERROR_WANT_CONNECT"
		: err_no == SSL_ERROR_WANT_ACCEPT ? "SSL_ERROR_WANT_ACCEPT"
		: err_no == SSL_ERROR_WANT_X509_LOOKUP ? "SSL_ERROR_WANT_X509_LOOKUP"
		: err_no == SSL_ERROR_SYSCALL ? "SSL_ERROR_SYSCALL"
		: err_no == SSL_ERROR_SSL ? "SSL_ERROR_SSL"
		: err_no == SSL_ERROR_NONE ? "SSL_ERROR_SSL"
		: boost::lexical_cast<std::string>( err_no );

	err_stream << "SSLClient: " << err_msg << "; " << err_type
			<< ": " << ERR_reason_error_string( ERR_get_error() );
	throw CommError(FailedConnection, err_stream.str() );
}

// ---------------------------------------------------------------- //

int kc_sf::getPasswordCallback (char *buf, int size, int rwflag, void *userdata)
{
	buf = (char *)"password"; // passwd is password
    return strlen(buf);
}

// ---------------------------------------------------------------- //

void kc_sf::sigpipeHandle(int x)
{
}


